﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Borg.Properties;

namespace Borg
{
    public abstract class Persistor : RuntimeElement
    {
        protected Dictionary<string, object> data;

        protected bool isModified;
        protected DateTime lastModified;

        private readonly string _path;
        private bool _isSaving;

        internal bool IsModified
        {
            get { return isModified; }
            private set
            {
                isModified = value;

                if (!value)
                    return;

                lastModified = DateTime.Now;

                if (_isSaving)
                    return;

                Runtime.Composer.InvokeOnCompositionDone(SaveAsynchronous);
                _isSaving = true;
            }
        }

        protected string Path
        {
            get
            {
                return
                    !string.IsNullOrEmpty(_path)
                        ? _path
                        : "Persistor.dat";
            }
        }

        protected internal Persistor(Runtime runtime, string path)
            : base(runtime)
        {
            _path = path;
        }

        internal void Init()
        {
            Load();
        }

        public void Save()
        {
            if (!CheckAccess())
                throw GetAccessException();
            
            InternalSave();
        }

        public bool Load()
        {
            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalLoad();
        }

        public void Save(Extension extension, string key, object value)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            if (key == null)
                throw new ArgumentNullException("key");

            if (key == string.Empty)
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");

            if (!CheckAccess())
                throw GetAccessException();
            
            InternalSave(extension, key, value);
        }

        public bool Restore<T>(Extension extension, string key, out T value)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");
            
            if (key == null)
                throw new ArgumentNullException("key");
            
            if (key == string.Empty)
                throw new ArgumentException(Exceptions.Key_must_not_be_empty, "key");
            
            if (!CheckAccess())
                throw GetAccessException();
            
            return InternalRestore(extension, key, out value);
        }

        public void Clear(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            if (!CheckAccess())
                throw GetAccessException();
            
            InternalClear(extension);
        }

        private void InternalSave(Extension extension, string key, object value)
        {
            data[GetKey(extension, key)] = value;
            IsModified = true;
        }

        private bool InternalRestore<T>(Extension extension, string key, out T value)
        {
            var extKey = GetKey(extension, key);

            if (!data.ContainsKey(extKey))
            {
                value = default(T);
                return false;
            }
            
            value = (T)data[extKey];

            return true;
        }

        private void InternalClear(Extension extension)
        {
            var pattern = GetKey(extension, string.Empty);
            
            foreach (var key in data.Keys.ToArray().Where(key => key.StartsWith(pattern)))
                data.Remove(key);
            
            IsModified = true;
        }

        protected abstract void InternalSave();

        protected abstract bool InternalLoad();

        private void SaveAsynchronous()
        {
            ThreadPool.QueueUserWorkItem(WaitAndSave);
        }

        private void WaitAndSave(object state)
        {
            do
            {
                Thread.Sleep(500);
            }
            while (DateTime.Now.Ticks - lastModified.Ticks < 500L);

            if (!CheckAccess())
                throw GetAccessException();

            Thread.CurrentThread.IsBackground = false;

            if (isModified)
                InternalSave();
            
            _isSaving = false;
            Thread.CurrentThread.IsBackground = true;
        }

        private static string GetKey(Extension extension, string key)
        {
            return
                string.Format("{0};{1};{2}",
                              extension.ExtensionType.Plugin.Name, extension.Name, key);
        }
    }
}

